package pt.um.jspecput.specifications;

import jargs.gnu.CmdLineParser;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import pt.um.jspecput.util.Constants;
import pt.um.jspecput.util.ExitException;
import pt.um.jspecput.util.NoExitSecurityManager;

import com.google.common.base.Strings;

import daikon.Chicory;
import daikon.FileIO;
import daikon.PptMap;

/**
 * The Class DaikonIntegration is responsible for the bridge between JSpecPUT
 * and the Daikon Invariant detector.
 */
public abstract class DaikonIntegration {

    /** The logger object for this class. */
    private static final Log LOG = LogFactory.getLog(DaikonIntegration.class);

    /** The usage message for this class. */
    private static final String USAGE_MESSAGE = "Usage: DaikonIntegration [--prefix=<project_prefix>]"
            + "[--heap-size=<java_heap_size>] [--inv-file=<output_file>] file_to_execute";

    /** The command line option parser. */
    private static CmdLineParser parser;

    /** The project prefix. */
    private static String projectPrefix;

    /** The heap size. */
    private static String heapSize;

    /** The invariant file name. */
    private static String invFileName;

    /** The remaining command line arguments. */
    private static String[] remainingArgs;

    /** The invariant map. */
    private static PptMap invariantsMap;

    /**
     * Gets the invariant map.
     * 
     * @return the invariants map
     */
    public static PptMap getInvariantsMap() {
        return invariantsMap;
    }

    /**
     * Sets the invariant map.
     * 
     * @param map
     *            the new invariant map
     */
    protected static void setInvariantsMap(PptMap map) {
        invariantsMap = map;
    }
    
    /**
     * Gets the project prefix.
     *
     * @return the project prefix
     */
    public static String getProjectPrefix() {
        return projectPrefix;
    }

    /**
     * Sets the project prefix.
     *
     * @param projectPrefix the new project prefix
     */
    public static void setProjectPrefix(String projectPrefix) {
        DaikonIntegration.projectPrefix = projectPrefix;
    }

    /**
     * Gets the heap size.
     *
     * @return the heap size
     */
    public static String getHeapSize() {
        return heapSize;
    }

    /**
     * Sets the heap size.
     *
     * @param heapSize the new heap size
     */
    public static void setHeapSize(String heapSize) {
        DaikonIntegration.heapSize = heapSize;
    }

    /**
     * Gets the inv file name.
     *
     * @return the inv file name
     */
    public static String getInvFileName() {
        return invFileName;
    }

    /**
     * Sets the inv file name.
     *
     * @param invFileName the new inv file name
     */
    public static void setInvFileName(String invFileName) {
        DaikonIntegration.invFileName = invFileName;
    }

    /**
     * Gets the remaining args.
     *
     * @return the remaining args
     */
    public static String[] getRemainingArgs() {
        return remainingArgs;
    }

    /**
     * Sets the remaining args.
     *
     * @param remainingArgs the new remaining args
     */
    public static void setRemainingArgs(String[] remainingArgs) {
        DaikonIntegration.remainingArgs = remainingArgs;
    }
    
    /**
     * The main method. It can be invoked from the command line.
     * 
     * @param args
     *            the command line arguments
     */
    public static void main(final String[] args) {
        processArgs(args);

        inferSpecifications(projectPrefix, heapSize, invFileName, remainingArgs);
    }

    /**
     * Infer specifications from target application.
     * 
     * @param projectPrefix
     *            the project prefix
     * @param heapSize
     *            the VM heap size
     * @param invFileName
     *            the file name to store the invariants
     * @param remainingArgs
     *            the target application
     */
    public static void inferSpecifications(String projectPrefix,
            String heapSize, String invFileName, String[] remainingArgs) {

        String[] chicoryArgs = setUpChicory(projectPrefix, heapSize,
                invFileName, remainingArgs);

        runChicory(chicoryArgs);

        readInvariants();
    }

    /**
     * Process the command line arguments and set the fields appropriately.
     * 
     * @param args
     *            the arguments to process
     */
    protected static void processArgs(String[] args) {
        parser = new CmdLineParser();

        CmdLineParser.Option prefix = parser.addStringOption("prefix");
        CmdLineParser.Option heapS = parser.addStringOption("heap-size");
        CmdLineParser.Option invFN = parser.addStringOption("inv-file");

        try {
            parser.parse(args);

        } catch (CmdLineParser.OptionException e) {
            LOG.fatal(e.getMessage());
            LOG.fatal(USAGE_MESSAGE);
            e.printStackTrace();
        }

        projectPrefix = (String) parser.getOptionValue(prefix);
        heapSize = (String) parser.getOptionValue(heapS,
                Constants.DEFAULT_HEAP_SIZE);
        invFileName = (String) parser.getOptionValue(invFN,
                Constants.DEFAULT_OUTPUT_FILE);
        remainingArgs = parser.getRemainingArgs();
        if (remainingArgs == null
                || Strings.isNullOrEmpty(Arrays.toString(remainingArgs))) {
            LOG.fatal("Target class missing.");
            LOG.fatal(USAGE_MESSAGE);
        }

    }

    /**
     * Sets the up the arguments to run Chicory.
     * 
     * @param projectPrefix
     *            the project prefix
     * @param heapSize
     *            the VM heap size
     * @param invFileName
     *            the file name to store the invariants
     * @param remainingArgs
     *            the target application
     * @return the a proper argument array to pass on to Chicory
     */
    protected static String[] setUpChicory(String projectPrefix,
            String heapSize, String invFileName, String[] remainingArgs) {
        ArrayList<String> auxArgs = new ArrayList<String>();
        String[] newArgs = new String[auxArgs.size()];

        auxArgs.add("--daikon");

        if (projectPrefix != null) {
            auxArgs.add("--ppt-select-pattern=" + projectPrefix);
        }

        if (heapSize != null) {
            auxArgs.add("--heap-size=" + heapSize);
        }

        if (invFileName != null) {
            auxArgs.add("--dtrace-file=" + invFileName + ".dtrace.gz");
        }

        auxArgs.addAll(Arrays.asList(remainingArgs));
        newArgs = auxArgs.toArray(newArgs);
        return newArgs;
    }

    /**
     * Read the invariant file and store them in a Map.
     */
    protected static void readInvariants() {
        try {
            invariantsMap = FileIO.read_serialized_pptmap(new File(invFileName
                    + ".inv.gz"), true);
        } catch (IOException e) {
            LOG.fatal("Something went wrong while reading the invariant file.");
            LOG.fatal(e.getMessage());
            System.exit(1);
        }
    }

    /**
     * Invoke Chicory.
     * 
     * @param args
     *            the arguments to pass on to Chicory
     */
    protected static void runChicory(String[] args) {
        SecurityManager defaultSM = System.getSecurityManager();
        SecurityManager sm = new NoExitSecurityManager();
        System.setSecurityManager(sm);

        try {
            Chicory.main(args);
        } catch (ExitException e) {
            // Silence the exception.
        }

        System.setSecurityManager(defaultSM);
    }
}
